import re
from pony import orm

from .cosine_similary import cosine_similarity_main
from ..models.db import MedicalServiceCutWord, MedicalServiceNormalize


class MedicalServiceNormalization:

    def __init__(self):
        pass

    def is_number(self, ch):
        """判断一个unicode是否是数字"""
        if u'\u0030' <= ch <= u'\u0039':
            return True
        else:
            return False

    def pre_processing(self, name):
        """
        预处理。
        :param name:输入字符串
        :return:预处理后字符串
        """
        # 去空格
        name = "".join(name.split())
        name = name.upper()
        name = name.replace('㎎', 'mg')
        name = name.replace('Ⅻ', 'XII')
        name = name.replace('Ⅺ', 'XI')
        name = name.replace('Ⅹ', 'X')
        name = name.replace('Ⅸ', 'IX')
        name = name.replace('Ⅷ', 'VIII')
        name = name.replace('Ⅶ', 'VII')
        name = name.replace('Ⅵ', 'VI')
        name = name.replace('Ⅴ', 'V')
        name = name.replace('Ⅳ', 'IV')
        name = name.replace('Ⅲ', 'III')
        name = name.replace('Ⅱ', 'II')
        name = name.replace('Ⅰ', 'I')
        name = self.strQ2B(name)
        name = name.lower()
        # 统一转为英文符号
        # intab = u"—；。：，【】（）；“”‘’？！｛｝《》／—「」『』〖〗〔〕＊＿＿＋＼〓"
        # outab = u"-;.:,[]();\"\"\'\'?!{}<>/-[][][]()*--+/="
        intab = u"—；。：，【】（）；“”‘’？！｛｝《》／—「」『』〖〗〔〕＊＿_＿＋＼〓[]αβγ∽∶‥–‒Ι×､{}"
        outab = u"-;.:,()();\"\"\'\'?!()()/-()()()()*---+/=()aby~::--i*、()"
        trantab = str.maketrans(intab, outab)  # 一一映射。
        name = name.translate(trantab)  # 返回字符串S的副本，其中每个字符都已通过给定的转换表进行映射。
        # 去特殊符号
        special_char = re.compile("[■□●○◎◇◆△▲＠@!$￥＆&＾^?§№☆★※→←↑↓°①②•˙∙·®`]")  #
        name = re.sub(special_char, "", name)
        if self.is_number(name[0]):
            name = name[1:]
        return name

    def remove_char(self, name):
        # 去符号
        special_char = re.compile("[-;.:,()\"\'?!/*+=、]")  #
        name = re.sub(special_char, "", name)
        name = name.replace("\\", "")
        return name

    def strQ2B(self, ustring):
        """全角转半角"""
        rstring = ""
        for uchar in ustring:
            inside_code = ord(uchar)
            if inside_code == 12288:  # 全角空格直接转换
                inside_code = 32
            elif (65281 <= inside_code <= 65374):  # 全角字符（除空格）根据关系转化
                inside_code -= 65248

            rstring += chr(inside_code)
        return rstring

    def medical_service_synonym_match(self, enter):
        """
        精准匹配:医疗服务同义词库。
        :param enter: 输入字符串
        :return: 精准匹配结果
        """
        with orm.db_session:
            candidate_medicine_list = list(orm.select(
                medical_service.standard_name
                for medical_service in MedicalServiceNormalize
                if
                medical_service.other_name == enter
                or
                medical_service.standard_name == enter
            ))

            return candidate_medicine_list

    def medical_service_accurate_match(self, input_medical_service, processed_medical_service):
        # 如果有录入医疗服务,尝试用录入药品精准匹配同义词库,注意这里可能需要做预处理
        if input_medical_service:
            normalized_result = self.medical_service_synonym_match(input_medical_service)
            if normalized_result:
                return normalized_result, 1
        # 用处理后的医疗服务做同义词库的精准匹配
        normalized_result = self.medical_service_synonym_match(processed_medical_service)
        if normalized_result:
            return normalized_result, 1
        return normalized_result, 0


    def match_by_cut_words(self, input_medical_service):
        # TODO 因为是切词库匹配，理论上说括号中的内容放在词条中的任何位置是不会影响该切词是否存在于该词条中的判断，待定
        candidate_medical_service_list = []
        candidate_medical_service_dict = {}
        with orm.db_session:
            candidate_city_list = list(orm.select(
                (medical_service.name,
                 medical_service.cut_word_name,
                 medical_service.cut_word_type,
                 medical_service.cut_word_property,
                 medical_service.cut_word_property_num,
                 )
                for medical_service in MedicalServiceCutWord
                if
                medical_service.cut_word_name in input_medical_service
            ).order_by(orm.raw_sql('SIMILARITY(medical_service.name, $input_medical_service) DESC')).limit(200))

        for data in candidate_city_list:
            cut_word_property = data[3]
            if data[0] not in candidate_medical_service_dict.keys():
                candidate_medical_service_dict[data[0]] = {'cut_word_list': [data[1]],
                                                           'cut_word_property_num':data[4],
                                                           'cut_word_property_list':[data[3]]}
            else:
                if cut_word_property not in candidate_medical_service_dict[data[0]]['cut_word_property_list']:
                    candidate_medical_service_dict[data[0]]['cut_word_property_list'].append(cut_word_property)
                if data[1] not in candidate_medical_service_dict[data[0]]['cut_word_list']:
                    candidate_medical_service_dict[data[0]]['cut_word_list'].append(data[1])

        for candidate_medical_service,v in candidate_medical_service_dict.items():
            # 去除切词库中的包含项
            cut_word_list = sorted(v['cut_word_list'],reverse=True)
            for i in range(len(cut_word_list)-1,0,-1):
                if cut_word_list[i] in cut_word_list[i-1]:
                    cut_word_list.remove(cut_word_list[i])

            cut_word_concat = "".join(cut_word_list)
            cut_word_matched_score = (25-v['cut_word_property_num'] + len(v['cut_word_property_list']))/25
            similarity_score = cosine_similarity_main(candidate_medical_service, input_medical_service)
            cut_word_similarity_score = cosine_similarity_main(cut_word_concat, input_medical_service)

            candidate_medical_service_list.append({'candidate_medical_service':candidate_medical_service,
                                                  'similarity_score': similarity_score,
                                                  'cut_word_matched_score':cut_word_matched_score,
                                                  'cut_word_similarity_score':cut_word_similarity_score,
                                                  'cut_word_property_num':v['cut_word_property_num'],
                                                  'cut_word_property_list_length':len(v['cut_word_property_list']),
                                                  'cut_word_property_list':v['cut_word_property_list'],
                                                  'cut_word_list_length':len(cut_word_list),
                                                  'cut_word_list':cut_word_list,
                                                  'cut_word_concat':cut_word_concat,
                                                  'cut_word_concat_length': len(cut_word_concat)
                                                  })

        return candidate_medical_service_list

    def normalize(self, input_medical_service):
        '''
        :param input:字符串。
        :return:归一化结果
        '''
        # 如果输入为空，则直接返回
        if input_medical_service == "":
            return []
        # 对输入词条进行预处理,统一符号并去除一些特殊符号
        input_medical_service = self.pre_processing(input_medical_service)
        # TODO 精准匹配，已经在外面做过了，但是需要注意这里做了特殊符号去除，整体框架需要调整
        # normalized_result = self.match_by_nhsa_medical_service_name(input_medical_service)
        # if normalized_result:
        #     return [[normalized_result], 1]
        # TODO 同义词匹配，已经在前面做过了，但是需要注意 同义词匹配前做了 self.remove_char(new_entry) 这个操作，整体框架需要调整
        # normalized_result = self.match_by_nhsa_medical_service_aliases(input_medical_service)
        # if normalized_result:
        #     return [[normalized_result], 1]

        normalized_result_list = self.match_by_cut_words(input_medical_service)

        #最长的匹配字符数
        if not normalized_result_list:
            return [""]
        max_len_match = sorted(normalized_result_list, key=lambda x: x['cut_word_concat_length'], reverse=True)[0]['cut_word_concat_length']
        # # 最多的切词属性匹配类型个数 ，这两的执行顺序会影响后面
        max_matched_num = sorted(normalized_result_list, key=lambda x: x['cut_word_property_list_length'], reverse=True)[0]['cut_word_property_list_length']

        for data in normalized_result_list:
            # # 如果匹配中的切词类型与该候选值拥有的切词类型数量一致，则认为自动通过
            # # 有例外 ‘血清果糖测定’ ‘血清浓度测定123’ ‘血清123测定’
            # # TODO 库里统计个数时需要把‘其他’这项去除
            # if data['cut_word_property_list_length'] == data['cut_word_property_num'] == max_matched_num:
            #     return [[data], 1]

            # 输入词条去除匹配中的切词列表后，如果结果为空，则自动通过。
            # TODO 有可能出现 类似输入为‘浓度测定’，匹配到一大堆‘××浓度测定’结果自动通过的情况
            remove_char_medical_service = self.remove_char(input_medical_service)
            for cut_word in data['cut_word_list']:
                remove_char_medical_service = remove_char_medical_service.replace(cut_word,'')
            if remove_char_medical_service == '':
                return [[data], 1]
            data['remove_score'] = 1-len(remove_char_medical_service) / len(input_medical_service)
            # 如果一个切词属性都没有匹配到
            if max_matched_num == 0:
                data['merge_score'] = 0.1 * data['remove_score'] + 0.9 * round(data['similarity_score'] * 0.25 + data['cut_word_similarity_score'] * 0.25 + data['cut_word_matched_score'] * 0.5, 2)
            # 有任意的切词属性被匹配到了
            else:
                data['merge_score'] = 0.1 * data['remove_score'] + 0.9 * \
                                      round(data['similarity_score'] * 0.15 +
                                            data['cut_word_similarity_score'] * 0.25 +
                                            data['cut_word_matched_score'] * 0.4 +
                                            data['cut_word_property_list_length'] / data['cut_word_property_num'] * 0.05 +
                                            data['cut_word_property_list_length'] / max_matched_num * 0.1 +
                                            data['cut_word_concat_length'] / max_len_match * 0.05, 2)

        # 按照最后的匹配分数进行排序
        normalized_result_list.sort(key=lambda x:x['merge_score'], reverse=True)

        return normalized_result_list[:10]


if __name__ == '__main__':
    import time

    input = "普通"
    t1 = time.time()
    medical_service_normalization = MedicalServiceNormalization()
    # result = medical_service_normalization.normalize(input)
    # print("耗时=", time.time() - t1)
    # print(result)

    result0 = medical_service_normalization.leshan_medical_service_search("普通", '', '龙岩市')
    print(result0)
